/*---------------------------------------------------------------------------
  NAME:
    binary_read
  
  CALLING SEQUENCE:
    IDL> struct = binary_read(file/lun, structdef, numrows, 
                              rows=, columns=, status=, verbose=, /help)

  PURPOSE:

    Read unformatted binary data from a file into a structure.  The user inputs
    the structure definition that describes each "row" of the file.  The user
    can extract certain columns and rows by number. For very large files, this
    is a big advantage over the readu which can only read contiguous chunks.
    The return variable is a structure containing the requested data.  Variable
    length columns are not currently supported.

    The columns of the input file must be fixed length, and this includes
    strings.
    
    The user can send the file name, in which case the data must start at the
    beginning of the file, or the file unit opened from IDL.  The file must be
    opened with the /stdio keyword and bufsize=0.

    In general, due to the complex inputs and the fact that most files will
    have a header describing the data, this program will be used as a utility
    program and an IDL wrapper will parse the header and format the structure
    definition.

    This program is written in C and is linked to IDL via the DLM mechanism.

  INPUTS: 
     file/lun: Filename or file unit. For string file names, the user must 
               expand all ~ or other environment variables.  If the file
	       unit is entered, the file must be opened with the appropriate 
	       keywords:
                 openr, lun, file, /get_lun, /stdio, bufsize=0
     structdef: A structure that describes the layout of the data in each row.
                Variable length fields are not supported.
     numrows: Number of rows in the file.

  OPTIONAL INPUTS:
     rows=: An array or scalar of unique rows to read
     columns=: An array or scalar of unique columns numbers to extract.
     verbose=: 0 for standard quiet. 1 for Basic info. > 1 for debug mode.
     /help: Print this message, full documentation.

  OPTIONAL OUTPUTS:
    status=: The status of the read. 0 for success, 1 for read failure, 
             2 for input errors such as bad file unit.

  TODO:

    Might write support for variable length columns, such as for strings.  This
    would need to write a binary_write.c to write them properly.  Would
    probably require the user to specify which columns are variable and the
    first n bytes of the field to describe the length. One byte supportes
    strings of length 255, two bytes would support 65,535 length strings, four
    4,294,967,295

  REVISION HISTORY:
    Created 20-April-2006: Erin Sheldon, NYU


  Copyright (C) 2005  Erin Sheldon, NYU.  erin dot sheldon at gmail dot com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


  ---------------------------------------------------------------------------*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "export.h"
#include "fileio_util.h"
#include "binary_read.h"

KW_RESULT kw;
/* int8 verbose; */

IDL_VPTR
binary_read(int argc, IDL_VPTR *argv, char *argk)
{

  /* Input structure, file, nrows, rows=, columns= */
  IDL_VPTR instructVptr, fileVptr, nrowsVptr;
  char *file;
  IDL_VPTR rowsVptr;
  IDL_MEMINT nrows, row, row2read, nrows2read, irow2read; 
  IDL_MEMINT_VECTOR *rows;
  FILE *FILEPTR;

  /* offsets will depend on whether or not we are requesting columns */
  IDL_MEMINT *tagOffsets;

  IDL_VPTR columnsVptr;
  int8 *get_column;
  int16 *columns;
  IDL_MEMINT ncolumns;

  /* Tag information */

  idl_tag_info *input_tag_info, *tag_info;
  int32 input_tag, tag;

  IDL_VPTR resultVptr, tmpVptr;
  UCHAR *dataPtr, *tptr, *rowptr;

  IDL_STRING *tStringPtr;

  IDL_STRUCT_TAG_DEF *tagdefs;
  int nparams;

  /* verbosity level */
  int verbose=0, debug=0;

  /* Loop variables */
  IDL_MEMINT i;
  int nread;

  /* messages */
  char mess[100];
  int read_error;

  br_set_status(READ_FAILURE);



  /* Process the keywords */
  (void) IDL_KWProcessByOffset(argc, argv, argk, kw_pars, 
			       (IDL_VPTR *) 0, 1, &kw);


  /* Print detailed documentation if requested */
  if (kw.help_there) {
    if (kw.help) {
      br_syntax(1);
      return(IDL_GettmpInt(-1));
    }
  }


  /* Check # of parameters */
  nparams = br_nparams(argc);
  if (nparams < 3) {
    br_syntax(0);
    return(IDL_GettmpInt(-1));
  }



  /* Get inputs */
  fileVptr = argv[0];
  instructVptr = argv[1];
  nrowsVptr = argv[2];



  /* Get the file pointer. */
  if (! (FILEPTR = get_fileptr(fileVptr,"r")))
    {
      br_set_status(INPUT_ERROR);      
      return(IDL_GettmpInt(-1));  
    }

  /* Get number of rows to read */
  nrows = IDL_MEMINTScalar(nrowsVptr);

  if (kw.verbose_there)
    {
      verbose = kw.verbose;
      if (verbose > 1) debug=1;
    }


  /*
   * Keyword Data 
   */

  /* Did user request certain rows? */
  if ( kw.rows_there )
    {
      rows = getrows(kw.rows, nrows);
      if (rows == NULL)
	{
	  error_message("Keyword rows contains no valid rows",NULL);
	  ar_set_status(INPUT_ERROR);      
	  return(IDL_GettmpInt(-1));  
	}

      nrows2read = rows->n;
      if (verbose) {
	sprintf(mess,"Extracting %d/%d rows",nrows2read,nrows);
	message(mess);
      }
    }
  else 
    {
      nrows2read = nrows;
      if (verbose) {
	sprintf(mess,"Reading %d rows",nrows2read);
	message(mess);
      }
    }


  /* Get tag info from the input structure */
  input_tag_info = get_idl_tag_info(instructVptr, verbose);


  /* Did the user request certain columns? This will determine the
     output */
  /* WARNING currently columns must be numbers and these must be sorted 
     and unique! */

  if (debug) message("Getting columns");//;fflush(stdout);
  dataPtr = get_output(instructVptr,
		       kw.columns_there, kw.columns, 
		       input_tag_info,
		       nrows2read,
		       &resultVptr,
		       &tagOffsets,
		       &get_column, verbose);
  if (dataPtr == NULL)
    {
      br_set_status(INPUT_ERROR);
      free_tag_info(input_tag_info);
      if (fileVptr->type == IDL_TYP_STRING)
	fclose(FILEPTR);

      return(IDL_GettmpInt(-1));  

    }
  if (debug) message("Done columns");//;fflush(stdout);

  /* read the data.  The calls above could also be used for an ascii reader.
     The code below would have to be rewritten to deal with the issues of
     reading ascii */

  read_error = 0;
  row = 0;
  for (irow2read=0; irow2read<nrows2read; irow2read++)
    {


      /* Are we skipping any rows? If not, then we just read */
      if (kw.rows_there) 
	{
	  row2read = rows->d[irow2read];
	  if (row < row2read)
	    {

	      if (fseek(FILEPTR, 
			input_tag_info->row_nbytes*(row2read-row),
			SEEK_CUR) != 0)
		{
		  message("Requested data beyond EOF");
		  read_error = 1;
		  break;
		}
	      row = row2read;
	    }
	}

      /* Loop over all available tags */
      tag = 0;
      for (input_tag=0;input_tag<input_tag_info->ntags; input_tag++)
	{
	      
	  /* Are we saving this info in output struct? */
	      
	  if (get_column[input_tag])
	    {
		  		  
	      tptr = 
		( resultVptr->value.s.arr->data +
		  irow2read*( resultVptr->value.arr->elt_len ) + 
		  tagOffsets[tag]);
		  
		  
	      if (input_tag_info->tagDesc[input_tag]->type != IDL_TYP_STRING)
		{
		  nread = fread(tptr, 
				input_tag_info->tagNbytes[input_tag], 
				input_tag_info->tagNelts[input_tag], 
				FILEPTR);

		  /* check for read errors */
		  if (nread != input_tag_info->tagNelts[input_tag])
		    {
		      read_error = 1;
		      break;
		    }
		}
	      else 
		{
		  /* Strings: Need to loop over and store each separately in a
		     buffer before copying to struct.  This will work for
		     scalars and arrays of strings.  Note we assume all strings
		     in array are same length as the example given in input
		     struct! */
		      
		  tStringPtr = (IDL_STRING *) tptr;
		  for (i=0; i<input_tag_info->tagNelts[input_tag]; i++)
		    {
		      nread = fread( input_tag_info->buffer[input_tag], 
				     input_tag_info->tagNbytes[input_tag], 
				     1, 
				     FILEPTR);

		      /* check for read errors */
		      if (nread != 1)
			{
			  read_error=1;
			  break;
			}

		      IDL_StrStore(tStringPtr, 
				   input_tag_info->buffer[input_tag]);
		      tStringPtr++;
		    }
		      
		}
	      /* Separately increment copied tags */
              tag++;

	    } /* Copying column into output variable */
	  else
	    {

	      /* Skipping this column */
	      if (fseek(FILEPTR, 
			input_tag_info->tagNbytes[input_tag]*input_tag_info->tagNelts[input_tag],
			SEEK_CUR) != 0)
		{
		  read_error=1;
		  break;
		}

	    } /* Not keeping this column */

	  if (read_error)
	    break;

	} /* Loop over columns */

      if (read_error)
	{
	  error_message_where(row,input_tag);
	  break;
	}

      row += 1;

    } /* Loop over rows*/

  free(get_column);
  free(tagOffsets);
  free_tag_info(input_tag_info);

  if (kw.rows_there)
    IDL_MEMINT_VECTOR_Free(rows);

  if (fileVptr->type == IDL_TYP_STRING)
    fclose(FILEPTR);


  IDL_KW_FREE;


  /* Was the read successful? */
  if (read_error)
    {
      /* I've decided it is better to return what we have read to help the user
	 diagnose the problem */
      br_set_status(READ_FAILURE);
      return(resultVptr);
      //IDL_Deltmp(resultVptr);
      //return(IDL_GettmpInt(-1));  
    }
  else
    {
      br_set_status(READ_SUCCESS);
      return(resultVptr);
    }


}






int br_nparams(int argc)
{

  int nKeywords;

  nKeywords =
    kw.columns_there + 
    kw.help_there +
    kw.rows_there + 
    kw.status_there + 
    kw.verbose_there;

  return 
    argc - nKeywords;
}


/* Put in main because uses kw */
void
br_set_status(int statusVal)
{

  //printf("In store status\n");fflush(stdout);
  if (kw.status_there) {
    /* This frees any existing memory and sets type to INT with value zero */
    //printf("Attempting to store zero int in status\n");fflush(stdout);
    IDL_StoreScalarZero(kw.status, IDL_TYP_INT);
    //printf("Attempting to a value in status\n");fflush(stdout);
    kw.status->value.i = statusVal;

    //printf("Successful status\n");fflush(stdout);
  }

}


void br_syntax(int help)
{

  if (help)
    {
      char docstring[] = "/*---------------------------------------------------------------------------\n  NAME:\n    binary_read\n  \n  CALLING SEQUENCE:\n    IDL> struct = binary_read(file/lun, structdef, numrows, \n                              rows=, columns=, status=, verbose=, /help)\n\n  PURPOSE:\n\n    Read unformatted binary data from a file into a structure.  The user inputs\n    the structure definition that describes each \"row\" of the file.  The user\n    can extract certain columns and rows by number. For very large files, this\n    is a big advantage over the readu which can only read contiguous chunks.\n    The return variable is a structure containing the requested data.  Variable\n    length columns are not currently supported.\n\n    The columns of the input file must be fixed length, and this includes\n    strings.\n    \n    The user can send the file name, in which case the data must start at the\n    beginning of the file, or the file unit opened from IDL.  The file must be\n    opened with the /stdio keyword and bufsize=0.\n\n    In general, due to the complex inputs and the fact that most files will\n    have a header describing the data, this program will be used as a utility\n    program and an IDL wrapper will parse the header and format the structure\n    definition.\n\n    This program is written in C and is linked to IDL via the DLM mechanism.\n\n  INPUTS: \n     file/lun: Filename or file unit. For string file names, the user must \n               expand all ~ or other environment variables.  If the file\n               unit is entered, the file must be opened with the appropriate \n        keywords:\n                 openr, lun, file, /get_lun, /stdio, bufsize=0\n     structdef: A structure that describes the layout of the data in each row.\n                Variable length fields are not supported.\n     numrows: Number of rows in the file.\n\n  OPTIONAL INPUTS:\n     rows=: An array or scalar of unique rows to read\n     columns=: An array or scalar of unique columns numbers to extract.\n     verbose=: 0 for standard quiet. 1 for Basic info. > 1 for debug mode.\n     /help: Print this message, full documentation.\n\n  OPTIONAL OUTPUTS:\n    status=: The status of the read. 0 for success, 1 for read failure, \n             2 for input errors such as bad file unit.\n\n  TODO:\n\n    Might write support for variable length columns, such as for strings.  This\n    would need to write a binary_write.c to write them properly.  Would\n    probably require the user to specify which columns are variable and the\n    first n bytes of the field to describe the length. One byte supportes\n    strings of length 255, two bytes would support 65,535 length strings, four\n    4,294,967,295\n\n  REVISION HISTORY:\n    Created 20-April-2006: Erin Sheldon, NYU\n\n\n  Copyright (C) 2005  Erin Sheldon, NYU.  erin dot sheldon at gmail dot com\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n\n\n  ---------------------------------------------------------------------------*/";
      printf("%s\n",docstring);
    }
  else
    {
      printf("\n");
      printf("   struct = binary_read(file/lun, structdef, numrows, \n");
      printf("                        rows=, columns=, status=, verbose=, /help)\n");
      printf("\n");
      printf("   Send /help for full documentation.\n");
    }

  fflush(stdout);

}



#define ARRLEN(arr) (sizeof(arr)/sizeof(arr[0]))

int IDL_Load(void)
{

  /* This must be static. It is a struct. */
  /* The name in strings is the name by which it will be called from IDL and
     MUST BE CAPITALIZED 
     5th parameter will say if it accepts keywords and some other flags 
     For more info see page 325 of external dev. guide */
  static IDL_SYSFUN_DEF2 func_addr[] = {
    { (IDL_SYSRTN_GENERIC) binary_read, "BINARY_READ", 
      0, IDL_MAXPARAMS, 
      IDL_SYSFUN_DEF_F_KEYWORDS, 0},
  };

  /* False means it is not a function */
  return IDL_SysRtnAdd(func_addr, IDL_TRUE, ARRLEN(func_addr));

}
